// RUN: %clang_cc1 -std=c++20 -verify %s
// expected-no-diagnostics

static constexpr int PRIMARY = 0;
static constexpr int SPECIALIZATION_CONCEPT = 1;
static constexpr int SPECIALIZATION_REQUIRES = 2;

template <class T>
concept Concept = (sizeof(T) >= 2 * sizeof(int));

struct XY {
  int x;
  int y;
};

namespace members {

template <class T, class U> struct S {
  static constexpr int primary();
};

template <class T, class U> constexpr int S<T, U>::primary() {
  return PRIMARY;
};

template <Concept C, class U> struct S<C, U> {
  static constexpr int specialization();
};

template <class T, class U>
  requires(sizeof(T) == sizeof(int))
struct S<T, U> {
  static constexpr int specialization();
};

template <Concept C, class U> constexpr int S<C, U>::specialization() {
  return SPECIALIZATION_CONCEPT;
}

template <class T, class U>
  requires(sizeof(T) == sizeof(int))
constexpr int S<T, U>::specialization() {
  return SPECIALIZATION_REQUIRES;
}

static_assert(S<char, double>::primary() == PRIMARY);
static_assert(S<XY, double>::specialization() == SPECIALIZATION_CONCEPT);
static_assert(S<int, double>::specialization() == SPECIALIZATION_REQUIRES);

} // namespace members

namespace enumerations {

template <class T, class U> struct S {
  enum class E : int;
};

template <class T, class U> enum class S<T, U>::E { Value = PRIMARY };

template <Concept C, class U> struct S<C, U> {
  enum class E : int;
};

template <Concept C, class U>
enum class S<C, U>::E {
  Value = SPECIALIZATION_CONCEPT
};

template <class T, class U>
  requires(sizeof(T) == sizeof(int))
struct S<T, U> {
  enum class E : int;
};

template <class T, class U>
  requires(sizeof(T) == sizeof(int))
enum class S<T, U>::E {
  Value = SPECIALIZATION_REQUIRES
};

static_assert(static_cast<int>(S<char, double>::E::Value) == PRIMARY);
static_assert(static_cast<int>(S<XY, double>::E::Value) ==
              SPECIALIZATION_CONCEPT);
static_assert(static_cast<int>(S<int, double>::E::Value) ==
              SPECIALIZATION_REQUIRES);

} // namespace  enumerations

namespace multiple_template_parameter_lists {

template <class Outer>
struct S {
  template <class Inner>
  static constexpr int primary(Inner);
};

template <class Outer>
template <class Inner>
constexpr int S<Outer>::primary(Inner) {
  return PRIMARY;
};

template <Concept Outer>
struct S<Outer> {
  template <class Inner>
  static constexpr int specialization(Inner);
};

template <Concept Outer>
template <class Inner>
constexpr int S<Outer>::specialization(Inner) { return SPECIALIZATION_CONCEPT; }

template <class Outer>
  requires(sizeof(Outer) == sizeof(int))
struct S<Outer> {
  template <class Inner>
  static constexpr int specialization(Inner);
};

template <class Outer>
  requires(sizeof(Outer) == sizeof(int))
template <class Inner>
constexpr int S<Outer>::specialization(Inner) { return SPECIALIZATION_REQUIRES; }

static_assert(S<char>::primary("str") == PRIMARY);
static_assert(S<XY>::specialization("str") == SPECIALIZATION_CONCEPT);
static_assert(S<int>::specialization("str") == SPECIALIZATION_REQUIRES);

} // namespace multiple_template_parameter_lists

static constexpr int CONSTRAINED_METHOD_1 = 1;
static constexpr int CONSTRAINED_METHOD_2 = 2;

namespace constrained_members {

template <int>
struct S {
  template <Concept C>
  static constexpr int constrained_method();
};

template <>
template <Concept C>
constexpr int S<1>::constrained_method() { return CONSTRAINED_METHOD_1; }

template <>
template <Concept C>
constexpr int S<2>::constrained_method() { return CONSTRAINED_METHOD_2; }

static_assert(S<1>::constrained_method<XY>() == CONSTRAINED_METHOD_1);
static_assert(S<2>::constrained_method<XY>() == CONSTRAINED_METHOD_2);


template <class T1, class T2>
concept ConceptT1T2 = true;

template<typename T3>
struct S12 {
  template<ConceptT1T2<T3> T4>
  static constexpr int constrained_method();
};

template<>
template<ConceptT1T2<int> T5>
constexpr int S12<int>::constrained_method() { return CONSTRAINED_METHOD_1; }

template<>
template<ConceptT1T2<double> T5>
constexpr int S12<double>::constrained_method() { return CONSTRAINED_METHOD_2; }

static_assert(S12<int>::constrained_method<XY>() == CONSTRAINED_METHOD_1);
static_assert(S12<double>::constrained_method<XY>() == CONSTRAINED_METHOD_2);

} // namespace constrained members

namespace constrained_members_of_nested_types {

template <int>
struct S {
  struct Inner0 {
    struct Inner1 {
      template <Concept C>
      static constexpr int constrained_method();
    };
  };
};

template <>
template <Concept C>
constexpr int S<1>::Inner0::Inner1::constrained_method() { return CONSTRAINED_METHOD_1; }

template <>
template <Concept C>
constexpr int S<2>::Inner0::Inner1::constrained_method() { return CONSTRAINED_METHOD_2; }

static_assert(S<1>::Inner0::Inner1::constrained_method<XY>() == CONSTRAINED_METHOD_1);
static_assert(S<2>::Inner0::Inner1::constrained_method<XY>() == CONSTRAINED_METHOD_2);


template <class T1, class T2>
concept ConceptT1T2 = true;

template<typename T3>
struct S12 {
  struct Inner0 {
    struct Inner1 {
      template<ConceptT1T2<T3> T4>
      static constexpr int constrained_method();
    };
  };
};

template<>
template<ConceptT1T2<int> T5>
constexpr int S12<int>::Inner0::Inner1::constrained_method() { return CONSTRAINED_METHOD_1; }

template<>
template<ConceptT1T2<double> T5>
constexpr int S12<double>::Inner0::Inner1::constrained_method() { return CONSTRAINED_METHOD_2; }

static_assert(S12<int>::Inner0::Inner1::constrained_method<XY>() == CONSTRAINED_METHOD_1);
static_assert(S12<double>::Inner0::Inner1::constrained_method<XY>() == CONSTRAINED_METHOD_2);

} // namespace constrained_members_of_nested_types

namespace constrained_member_sfinae {

template<int N> struct S {
  template<class T>
  static constexpr int constrained_method() requires (sizeof(int[N * 1073741824 + 4]) == 16) {
    return CONSTRAINED_METHOD_1;
  }

  template<class T>
  static constexpr int constrained_method() requires (sizeof(int[N]) == 16);
};

template<>
template<typename T>
constexpr int S<4>::constrained_method() requires (sizeof(int[4]) == 16) {
  return CONSTRAINED_METHOD_2;
}

// Verify that there is no amiguity in this case.
static_assert(S<4>::constrained_method<double>() == CONSTRAINED_METHOD_2);

} // namespace constrained_member_sfinae

namespace requires_expression_references_members {

void accept1(int x);
void accept2(XY xy);

template <class T> struct S {
  T Field = T();

  constexpr int constrained_method()
      requires requires { accept1(Field); };

  constexpr int constrained_method()
      requires requires { accept2(Field); };
};

template <class T>
constexpr int S<T>::constrained_method()
  requires requires { accept1(Field); } {
  return CONSTRAINED_METHOD_1;
}

template <class T>
constexpr int S<T>::constrained_method()
  requires requires { accept2(Field); } {
  return CONSTRAINED_METHOD_2;
}

static_assert(S<int>().constrained_method() == CONSTRAINED_METHOD_1);
static_assert(S<XY>().constrained_method() == CONSTRAINED_METHOD_2);

} // namespace requires_expression_references_members
